--[[                      -*- Mode: lua; lua-indent-level: 2; tab-width: 2 -*-
 ____   ___    _   _  ___ _____   _   _ ____  _____  __   _______ _____ _ 
|  _ \ / _ \  | \ | |/ _ \_   _| | | | / ___|| ____| \ \ / / ____|_   _| |
| | | | | | | |  \| | | | || |   | | | \___ \|  _|    \ V /|  _|   | | | |
| |_| | |_| | | |\  | |_| || |   | |_| |___) | |___    | | | |___  | | |_|
|____/ \___/  |_| \_|\___/ |_|    \___/|____/|_____|   |_| |_____| |_| (_)

+-----------------------------------------------------------------+
| _ _  _    ___  ____ _  _ ____ _    ____ ___  _  _ ____ _  _ ___ |\
| | |\ |    |  \ |___ |  | |___ |    |  | |__] |\/| |___ |\ |  |  ||
| | | \|    |__/ |___  \/  |___ |___ |__| |    |  | |___ | \|  |  ||
|                                                                 ||
+-----------------------------------------------------------------+|
 \_________________________________________________________________\
_    _ ___  ____ _ ____ _  _ ____ _       _ ____    ____ 
|    | |__] [__  | | __ |\ | |__| |       | [__     |__| 
|___ | |__] ___] | |__] | \| |  | |___    | ___]    |  | 
                                                         
____ _ _  _ ___  _    ____    ____ _  _ ____ _  _ ___ 
[__  | |\/| |__] |    |___    |___ |  | |___ |\ |  |  
___] | |  | |    |___ |___    |___  \/  |___ | \|  |  
                                                      
____ _ ____ _  _ ____ _    _ _  _ ____    _    _ ___  ____ ____ ____ _   _ 
[__  | | __ |\ | |__| |    | |\ | | __    |    | |__] |__/ |__| |__/  \_/  
___] | |__] | \| |  | |___ | | \| |__]    |___ | |__] |  \ |  | |  \   |   
                                                                           
____ ____ ____    _  _ ____ ____    ___  _   _    ____ ___ _  _ ____ ____ 
|___ |  | |__/    |  | [__  |___    |__]  \_/     |  |  |  |__| |___ |__/ 
|    |__| |  \    |__| ___] |___    |__]   |      |__|  |  |  | |___ |  \ 
                                                                          
_    _ ___  ____ ____ ____ _ ____ ____  
|    | |__] |__/ |__| |__/ | |___ [__   
|___ | |__] |  \ |  | |  \ | |___ ___] .

It is loosely inspired by LibStub, AceEvents-2.0, AceEvents-3.0 and
LibRockEvents-1.0.

-- ToDo : Pausing / Resuming Handles
--      : Further Performance Optimizations
--      : More extensive test cases
-- Once ToDo list is finished this library should be considered capped
-- all additional features should be addable at higher levels.
]]

local LibStub = _G.LibStub

-- The do block is to allow this code to be easily cut and pasted inside
-- another module.

do
	-- LibSignal is hereby placed in the Public Domain
	-- Author: Daphne Pfister

	-- Data types
	--  Signal Name - Signal names are hierarchical separated by colons(:),
  --                examples: "Namespace:Signal", "Namespace:Signal:SubCase"
	--              - When a signal is fired all listeners higher in the
  --                hierarchy are also invoked.
	--  Handle      - An arbitary hook used for a bunch of listeners. Normally
  --                should be the "class" that the owns the listeners
	--                Note: false is a valid handle.
  --  Listener    - A function called when a signal is fired
  --              - Listeners that return true are removed

	-- MINOR IS NOT AN SVN REVISION. NEEDS TO BE CONSISTANT ACROSS VERSIONING SYSTEMS
  -- MINOR WILL STAY 0 WHILE LibSignal IS IN INITIAL DEVELOPMENT
	local LIBSIGNAL_MAJOR, LIBSIGNAL_MINOR = "LibSignal-1.0", 0
	local LibSignal = LibStub:NewLibrary(LIBSIGNAL_MAJOR,LIBSIGNAL_MINOR)
	if LibSignal then
		local pairs = _G.pairs
		local geterrorhandler = _G.geterrorhandler
		local strmatch = strmatch
		local tinsert = table.insert
		local tmaxn = table.maxn

		-- All Named Signals
		if not LibSignal.signals_ then LibSignal.signals_ = {} end
		local signals_ = LibSignal.signals_
		-- Anonymous Signals
		if not LibSignal.signals_anon_ then LibSignal.signals_anon_ = {} end
		local signals_anon_ = LibSignal.signals_anon_

		-- All Handles and there Signals
		if not LibSignal.handles_ then LibSignal.handles_ = {} end
		local handles_ = LibSignal.handles_

		if not LibSignal.signal_mt_ then LibSignal.signal_mt_ = {} end
		local signal_mt_ = LibSignal.signal_mt_

		if not LibSignal.signal_proto_ then LibSignal.signal_proto_ = {} end
		local signal_proto_ = LibSignal.signal_proto_

		if not LibSignal.empty_tbl_ then LibSignal.empty_tbl_ = {} end
		local empty_tbl_ = LibSignal.empty_tbl_

		local StartListeningSignalName = "LibSignal:StartListening"
		local StopListeningSignalName = "LibSignal:StopListening"
		local StartListeningSignal
		local StopListeningSignal

		-- Prototypes
		local GetSignal

		-- Globally visible data
		-- These variables are only valid at the beginning of a listener
		-- Specifically using any LibSignal function inside a listener
		-- may invalidate them.
		LibSignal.signal = nil -- Currently firing signal
		LibSignal.handle = nil -- Handle of currently firing listener

		-- Private BuildWrapperFunc(func, ...)
		-- func (func) - function to call
		-- ... - initial arguments
		--
		-- returns a function that calls func following by the joining of the initial
		-- arguments and any following arguments
		local function BuildWrapperFunc(func, ...)
			-- Save the function and the parameters
			local dn = select('#',...)
			if (dn == 0) then
				-- Simple case: func
				return func
			elseif (dn == 1) then
				-- Optimized case: func,arg1
				local arg1 = select(1,...)
				return function (...) return func(arg1,...) end
			elseif (dn == 2) then
				-- Optimized case: func,arg1,arg2
				local arg1,arg2 = ...
				return function (...) return func(arg1,arg2,...) end
			else
				-- Generic case: func,arg1,arg2,...,argN
				-- Generate a wrapper on the fly that passes the arguments
				-- at the beginning of the function before any of the arguments
				-- are passed to the signal.

				-- This would be the equivalent of
				-- local t = {s=dn,...} return function (...) return func(unpack(t,1,t.s), ...) end
				-- if it actually worked...
				-- unfortunately lists can only be expanded at the end of a list

				local argtablemade
				local preamble = "local body = TEMP1; "
				local body = "return function (...) return body("
				for index=1,dn do
					local arg = select(index,...)
					local argtype = type(arg)
					if argtype == "nil" then
						arg = "nil"
					elseif argtype == "number" then
					elseif argtype == "string" then
						arg = arg.format('%q',arg)
					elseif argtype == "boolean" then
						arg = arg and "true" or "false"
					else
						if not argtablemade then
							_G.TEMP2 = {...}
							preamble = preamble.."local d = TEMP2; "
							argtablemade = true
						end
						preamble = preamble.."local arg"..index.." = d["..index.."]; "
						arg = "arg"..index
					end
					body = body..arg..","
				end
				body = preamble..body .. "...) end"
				--print(body)

				local temp1,temp2 = _G.TEMP1,_G.TEMP2
				_G.TEMP1 = func
				local bodyfunc,err = loadstring(body)
				if not bodyfunc then
					_G.TEMP1,_G.TEMP2 = temp1,temp2
					error(err)
				end
				local res = bodyfunc()
				_G.TEMP1,_G.TEMP2 = temp1,temp2
				return res
			end
		end

		-- Private ClearTable(res)
		-- res (table or nil) - Table to clear
		--
		-- returns res or a new table if res is nil
		local function ClearTable(res)
			if not res then return {} end
			for k in pairs(res) do
				res[k] = nil
			end
			return res
		end

		-- signal:__tostring()
		local function signal_tostring(self)
			local name = self.name or self.raw_name
			if name then return name end
			signal_mt_.__tostring = nil
			name = tostring(self)
			signal_mt_.__tostring = signal_tostring
			self.raw_name = "signal" .. name:sub(6)
			return self.raw_name
		end
		signal_mt_.__tostring = signal_tostring

		-- signal:Fire(...)
		-- ... - data to pass to signal listeners
		--
		-- return nils
		local function Fire(self,...)
			local base = self
			while self do
				local listeners = self.listeners
				for handle,listener in pairs(listeners) do
					LibSignal.handle = handle
					LibSignal.signal = base
					local success, res_or_err = pcall(listener,...)
					if success then
						if res_or_err == true then
							-- remove this listener
							listeners[handle] = nil
							if next(listeners)==nil then Fire(self.stop_listen,self) end
						end
					else
						geterrorhandler()(res_or_err)
					end
				end
				self = self.parent
			end
			return nil
		end
		signal_proto_.Fire = Fire
		signal_mt_.__call = Fire

		-- signal:HasListeners()
		--
		-- return nil if signal has no listeners
		-- return true if signal is being listened for (including indirectly at a higher level)
		local function HasListeners(self)
			while self do
				if next(self.listeners)~=nil then return true end
				self = self.parent
			end
			return nil
		end
		signal_proto_.HasListeners = HasListeners

		-- signal:HasExactListeners()
		--
		-- return nil if signal has no listeners
		-- return true if signal is being listened for directly
		local function HasExactListeners(self)
			return next(self.listeners)~=nil and true or nil
		end
		signal_proto_.HasExactListeners = HasExactListeners

		-- signal:RegisterListener(handle, func, ...)
		-- handle (any) - handle used to remove listener
    --                if handle is nil, func is used as handler
		-- func (function) - function to call when signal is process
		-- ... - additional arguments to give to function before signal arguments
		--
		-- returns nil
		--
		-- When a signal is fired func is called with the additional arguments
		-- given during registration as well as the arguments given to the
		-- FireSignal function.
		local function RegisterListener(self, handle, func, ...)
			if not func then return nil end
			local listeners = self.listeners
			local is_new = next(listeners)==nil
			if not handle then handle = func end
			listeners[handle] = BuildWrapperFunc(func,...)
			local handletbl = handles_[handle]
			if not handletbl then
				handles_[handle] = { [self] = true }
			else
				handletbl[self] = true
			end
			if is_new then Fire(self.start_listen,self) end
			return nil
		end
		signal_proto_.RegisterListener = RegisterListener

		-- signal:UnregisterListener(handle)
		-- handle (any) - handle used when first listening for signal
		--
		-- returns nil
		local function UnregisterListener(self, handle)
			local listeners = self.listeners
			if next(listeners)==nil then return nil end
			listeners[handle] = nil
			local handletbl = handles_[handle]
			if handletbl then handletbl[self] = nil end
			if next(listeners)==nil then Fire(self.stop_listen,self) end
			return nil
		end
		signal_proto_.UnregisterListener = UnregisterListener

		-- signal:GetKnownChildren([res])
		-- res (table) - Optional - Table for result
		--
		-- return table of signals
		-- NOTE: Only gets signals that have been looked up
		local function GetKnownChildren(self, res)
			res = ClearTable(res)
			for k in pairs(self.children) do
				res[k] = true
			end
			return res
		end
		signal_proto_.GetKnownChildren = GetKnownChildren

		-- signal:GetRegisteredChildren([res])
		-- res (table) - Optional - Table for result
		--
		-- return table of signals
		local function GetRegisteredChildren(self, res)
			res = ClearTable(res)
			for k in pairs(self.children) do
				if next(k.listeners)~=nil then
					res[k] = true
				end
			end
			return res
		end
		signal_proto_.GetRegisteredChildren = GetRegisteredChildren

		-- signal:GetKnownTree([res])
		-- res (table) - Optional - Table for result
		--
		-- return table of signals
		-- NOTE: Only gets signals that have been looked up.
		local function GetKnownTree(self, res)
			res = ClearTable(res)
			for k in pairs(self.tree) do
				res[k] = true
			end
			return res
		end
		signal_proto_.GetKnownTree = GetKnownTree

		-- signal:GetRegisteredTree([res])
		-- res (table) - Optional - Table for result
		--
		-- return table of signals
		local function GetRegisteredTree(self, res)
			res = ClearTable(res)
			for k in pairs(self.tree) do
				if next(k.listeners)~=nil then
					res[k] = true
				end
			end
			return res
		end
		signal_proto_.GetRegisteredTree = GetRegisteredTree

		-- signal:GetStartListeningSignal()
		--
		-- returns signal that gets fired when this signal is listened for
		local function GetStartListeningSignal(self)
			return self.start_listen
		end
		signal_proto_.GetStartListeningSignal = GetStartListeningSignal

		-- signal:GetStopListeningSignal()
		--
		-- returns signal that gets fired when this signal is no longer listened for
		local function GetStopListeningSignal(self)
			return self.stop_listen
		end
		signal_proto_.GetStopListeningSignal = GetStopListeningSignal


		-- signal:ClaimOwnership(handle)
		-- handle (any) - owner of signal
		--
		-- return nil if a different handle already owns the signal
		-- return true if handle now/already owns the signals
		local function ClaimOwnership(self,handle)
			if self.owner and self.owner ~= handle then return nil end
			self.owner = handle
			return true
		end
		signal_proto_.ClaimOwnership = ClaimOwnership

		-- signal:GetOwnership()
		--
		-- return nil if no owner exists
		-- otherwise return owner handle
		local function GetOwnership(self)
			return self.owner
		end
		signal_proto_.GetOwnership = GetOwnership

		-- signal:ReleaseOwnership(handle)
		-- handle (any) - owner of signal
		--
		-- return nil signal was owner by someone else
		-- return true if signal now has no owner
		local function ReleaseOwnership(self,handle)
			if self.owner == handle then self.owner = nil end
			return self.owner == nil and true or nil
		end
		signal_proto_.ReleaseOwnership = ReleaseOwnership

		-- Private GetNewSignal()
		--
		-- returns signal
		local function GetNewSignal()
			local signal = {
				listeners = {}
				, children = {}
				, tree = {}
			}
			signal.tree[signal] = true
			for k,v in pairs(signal_proto_) do
				signal[k] = v
			end
			setmetatable(signal, signal_mt_)
			return signal
		end

		-- LibSignal.GetAnonymousSignal()
		--
		-- returns signal
		local function GetAnonymousSignal()
			local signal = GetNewSignal()
			signal.root = signal
			signals_anon_[signal] = true
			-- This is safe since no anonymous signals are created in the library
			signal.start_listen = StartListeningSignal
			signal.stop_listen = StopListeningSignal
			return signal
		end
		LibSignal.GetAnonymousSignal = GetAnonymousSignal

		-- LibSignal.GetSignal(signalname)
		-- signalname (string) - name of the signal
		--
		-- returns signal
		local function GetSignal(signalname)
			local signal = signals_[signalname]
			if signal then return signal end
			local parent = strmatch(signalname,"(.*[^:]):")
			parent = parent and GetSignal(parent)
			signal = GetNewSignal()
			signal.parent = parent
			signal.name = signalname
			signal.root = parent and parent.root or signal
			signals_[signalname] = signal
			if parent then
				signal.start_listen = parent.start_listen
				signal.stop_listen = parent.stop_listen
				parent.children[signal] = true
				while parent do
					parent.tree[signal] = true
					parent = parent.parent
				end
			else
				local start_listen = GetSignal(StartListeningSignalName..":"..signalname)
				local stop_listen = GetSignal(StopListeningSignalName..":"..signalname)
				signal.start_listen = start_listen
				signal.stop_listen = stop_listen
				-- Special case to support LibSignal root
				for child in pairs(signal.children) do
					child.start_listen = start_listen
					child.stop_listen = stop_listen
				end
			end
			return signal
		end
		LibSignal.GetSignal = GetSignal

		-- LibSignal.FireSignal(signalname, ...)
		-- alias for LibSignal.GetSignal(signalname):Fire(...)
		function LibSignal.FireSignal(signalname,...)
			return Fire(GetSignal(signalname),...)
		end

		-- LibSignal.SignalHasListeners(signalname)
		-- alias for LibSignal.GetSignal(signalname):HasListeners()
		function LibSignal.SignalHasListeners(signalname)
			return HasListeners(GetSignal(signalname))
		end

		-- LibSignal.SignalHasExactListeners(signal)
		-- alias for LibSignal.GetSignal(signalname):HasExactListeners()
		function LibSignal.SignalHasExactListeners(signal)
			return HasExactListeners(GetSignal(signalname))
		end

		-- LibSignal.RegisterSignalListener(signalname, handle, func, ...)
		-- alias for LibSignal.GetSignal(signalname):RegisterListener(handle,func,...)
		function LibSignal.RegisterSignalListener(signalname, handle, func, ...)
			return RegisterListener(GetSignal(signalname),handle, func, ...)
		end

		-- LibSignal.UnregisterSignalListener(signal, handle)
		-- alias for LibSignal.GetSignal(signalname):UnregisterListener(handle)
		function LibSignal.UnregisterSignalListener(signalname, handle)
			return UnregisterListener(GetSignal(signalname),handle)
		end

		--GetRegisteredSignalsForRoot(root,[res])
		-- Alias for LibSignal.GetSignal(root):GetRegisteredTree([res])
		local function GetRegisteredSignalsForRoot(root,res)
			return GetRegisteredTree(GetSignal(root),res)
		end
		LibSignal.GetRegisteredSignalsForRoot = GetRegisteredSignalsForRoot

		-- LibSignal.UnregisterSignalHandle(handle)
		-- handle (any) - handle used when first registering for signal
		--
		-- returns nil
		local function UnregisterSignalHandle(handle)
			local handletbl = handles_[handle]
			for signal in pairs(handletbl or empty_tbl_) do
				local listeners = signal.listeners
				handletbl[signal] = nil
				if next(listeners)~=nil then
					listeners[handle] = nil
					if next(listeners)==nil then Fire(signal.stop_listen,signal) end
				end
			end
			return nil
		end
		LibSignal.UnregisterSignalHandle = UnregisterSignalHandle

		--GetSignalsForHandle(handle,[res])
		-- handle (any) - handle used when registering for signals
		-- res (table) - Optional - Table for result
		--
		-- return table of signals
		local function GetSignalsForHandle(handle,res)
			res = ClearTable(res)
			local handletbl = handles_[handle]
			for k in pairs(handletbl or empty_tbl_) do
				res[k] = true
			end
			return res
		end
		LibSignal.GetSignalsForHandle = GetSignalsForHandle

		--ClaimRoot(root,handle)
		-- Alias for LibSignal.GetSignal(root):ClaimOwnership(handle)
		local function ClaimRoot(root, handle)
			return ClaimOwnership(GetSignal(root),handle)
		end
		LibSignal.ClaimRoot = ClaimRoot

		--IsRootClaimed(root)
		-- Alias for LibSignal.GetSignal(root):GetOwnership() ~= nil
		local function IsRootClaimed(root)
			return GetOwnership(GetSignal(root)) ~= nil
		end

		-- Speed up access to some standard signals
		StartListeningSignal = GetSignal(StartListeningSignalName)
		StopListeningSignal = GetSignal(StopListeningSignalName)
		ClaimOwnership(GetSignal("LibSignal"),LibSignal)
	end

end
